package uk.ac.lkl.lpp.model;



import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.swing.event.ChangeListener;

import uk.ac.lkl.common.allocator.AllocatorModel;

import uk.ac.lkl.common.associator.Associator;
import uk.ac.lkl.common.associator.AssociatorHandle;

import uk.ac.lkl.common.util.ClassPair;
import uk.ac.lkl.common.util.ClassTriple;
import uk.ac.lkl.common.util.Property;

import uk.ac.lkl.common.util.ds.AbstractNotifyingList;
import uk.ac.lkl.common.util.ds.ListHandle;
import uk.ac.lkl.common.util.ds.NotifyingList;
import uk.ac.lkl.common.util.ds.NotifyingMap;

import uk.ac.lkl.lpp.model.ldo.event.*;
import uk.ac.lkl.common.util.ChangeSupport;


/**
 * The top-level model of the entire Pedagogy Planner.
 * 
 * It contains references to all entities used in the system. This is the
 * logical equivalent of the XML description of the pedagogy plan. As with the
 * XML representation, this class needs a lot of tidying up.
 * 
 * @author Darren Pearce
 * @version $Revision: 622 $
 * @version $Date: 2008-02-21 18:22:34 +0000 (Thu, 21 Feb 2008) $
 * 
 */
public class PedagogyPlanModel {

    /**
     * latest version of the data model scheme coders should update this when
     * the model changes. by convension, use svn version number.
     */
    public static final int LATEST_VERSION = 570;

    public static final String DEFAULT_TYPE      = "Module";
    public static final String DEFAULT_TIME_UNIT = "Hours";
    public static final String DEFAULT_SUB_TYPE  = "Session";

    /**
     * This indicated that the plan was modified since last saved
     * 
     */
    private boolean dirtyFlag;


    /**
     * what version of the data model scheme are we refering to? important for
     * patching old data files.
     */
    private int modelVersion = 0;

    /**
     * The support for <code>ChangeListener</code>s.
     * 
     */
    private ChangeSupport changeSupport;

    /**
     * ID as in XCRI. Typicaly a URI.
     */
    private String id;
    
    /**
     * Name
     */
    private String name;
    
    /**
     * long description, could be html.
     */
    private String description;
    
    
    /**
     * course / module / session / etc.
     */
    private String type = DEFAULT_TYPE;
    
    /**
     * days / hours / minutes 
     */
    private String time_unit = DEFAULT_TIME_UNIT;
    
    /**
     * type of sub-elements (e.g. Session for a Module) 
     */
    private String subType = DEFAULT_SUB_TYPE;
    
    /**
     * number of sub-units
     */
    private int nSubs;
    
    /**
     * credit time (in time_units)
     */
    private int creditTime;
    
    /**
     * contact time (in time_units)
     */
    private int contactTime;
    
    /**
     * study time (in time_units)
     */
    private int studyTime;
    
    /**
     * expected number of students
     */
    private int numStudents;

    /**
     * The set of associators for this instance.
     * 
     */
    private HashMap<AssociatorHandle, Associator> associators;


    /**
     * The set of collections for this instance.
     * 
     * Associators will relate two of these.
     * 
     */
    private HashMap<ListHandle, AbstractNotifyingList> lists;


    /**
     * Maps for this instance (e.g. teaching method hours allocation)
     * 
     */
    private HashMap<ClassPair, NotifyingMap> maps;


    /**
     * The allocators for this instance.
     * 
     */
    private HashMap<ListHandle, AllocatorModel> allocatorModels;


    private HashMap<String, Property> propertyMap;


    private ArrayList<PedagogyPlanListener> listeners;


    /**
     * Eventually, with plurals, etc, will need a more sophisticated approach
     * than this.
     * 
     */
    private HashMap<Class, String> conceptMap;

	public PedagogyPlanModel() {
        associators = new HashMap<AssociatorHandle, Associator>();
        lists = new HashMap<ListHandle, AbstractNotifyingList>();
        maps = new HashMap<ClassPair, NotifyingMap>();
        allocatorModels = new HashMap<ListHandle, AllocatorModel>();
        listeners = new ArrayList<PedagogyPlanListener>();
        propertyMap = new HashMap<String, Property>();
        conceptMap = new HashMap<Class, String>();
        changeSupport = new ChangeSupport(this);
        
    }


	/**
	 * @param id
	 * @param name
	 * @param description 
	 * @param type        module / session / period / lesson / etc.
	 * @param time_unit   time unit (weeks / days / hours / minutes / etc)
	 * @param subType     type of sub-units 
	 * @param subs        number of sub-units
	 * @param creditTime  
	 * @param contactTime teacher time
	 * @param studyTime   student time
	 * @param numStudents ideal number of students
	 */
	public PedagogyPlanModel(String id, String name, String description, String type, String time_unit, String subType, int subs, int creditTime, int contactTime, int studyTime, int numStudents) {
		this();
		this.id = id;
		this.name = name;
		this.description = description;
		this.type = type;
		this.time_unit = time_unit;
		this.subType = subType;
		nSubs = subs;
		this.creditTime = creditTime;
		this.contactTime = contactTime;
		this.studyTime = studyTime;
		this.numStudents = numStudents;
	}

    
    public int getContactTime() {
		return contactTime;
	}


	public void setContactTime(int contactTime) {
		this.contactTime = contactTime;
	}


	public int getCreditTime() {
		return creditTime;
	}


	public void setCreditTime(int creditTime) {
		this.creditTime = creditTime;
		fireStateChanged();
	}


	public String getDescription() {
		return description;
	}


	public void setDescription(String description) {
		this.description = description;
		fireStateChanged();
	}


	public String getId() {
		return id;
	}


	public void setId(String id) {
		this.id = id;
		fireStateChanged();
	}


	public String getName() {
		return name;
	}


	public void setName(String name) {
		this.name = name;
		fireStateChanged();
	}


	public int getNSubs() {
		return nSubs;
	}


	public void setNSubs(int subs) {
		nSubs = subs;
		fireStateChanged();
	}


	public int getNumStudents() {
		return numStudents;
	}


	public void setNumStudents(int numStudents) {
		this.numStudents = numStudents;
		fireStateChanged();
	}


	public int getStudyTime() {
		return studyTime;
	}


	public void setStudyTime(int studyTime) {
		this.studyTime = studyTime;
		fireStateChanged();
	}


	public String getSubType() {
		return subType;
	}


	public void setSubType(String subType) {
		this.subType = subType;
		fireStateChanged();
	}


	public String getTime_unit() {
		return time_unit;
	}


	public void setTime_unit(String time_unit) {
		this.time_unit = time_unit;
		fireStateChanged();
	}


	public String getType() {
		return type;
	}


	public void setType(String type) {
		this.type = type;
		fireStateChanged();
	}


    /**
     * what version of the data model scheme are we refering to? important for
     * patching old data files.
     */
    public int getVersion() {
        return modelVersion;
    }


    /**
     * what version of the data model scheme are we refering to? important for
     * patching old data files.
     * 
     * @return old value
     */
    public int setVersion(int version) {
        int tmp = modelVersion;
        modelVersion = version;
		fireStateChanged();
        return tmp;
    }


    public Map<AssociatorHandle, Associator> getAssociatorMap() {
        return Collections.unmodifiableMap(associators);
    }


    public Collection<Associator> getAssociators() {
        return associators.values();
    }


    public Collection<AssociatorHandle> getAssociatorClassTriples() {
        return associators.keySet();
    }


    public <S, A, D> Associator<S, A, D> getAssociator(Class<S> sourceClass,
                                                       Class<A> annotationClass,
                                                       Class<D> destinationClass) {
        return getAssociator(
                             null,
                             sourceClass,
                             annotationClass,
                             null,
                             destinationClass);
    }


    public <S, A, D> Associator<S, A, D> getAssociator(String sourceClassName,
                                                       Class<S> sourceClass,
                                                       Class<A> annotationClass,
                                                       String destinationClassName,
                                                       Class<D> destinationClass) {
        return getAssociator(new AssociatorHandle(sourceClassName, sourceClass,
                annotationClass, destinationClassName, destinationClass));
    }


    public Associator getAssociator(AssociatorHandle associatorHandle) {
        return associators.get(associatorHandle);
    }


    public <S, A, D> void addAssociator(Class<S> sourceClass,
                                        Class<A> annotationClass,
                                        Class<D> destinationClass,
                                        Associator<S, A, D> associator) {
        addAssociator(
                      null,
                      sourceClass,
                      annotationClass,
                      null,
                      destinationClass,
                      associator,
                      -1);
    }


    /**
     * 
     * @param <S>
     * @param <A>
     * @param <D>
     * @param sourceClassName
     * @param sourceClass
     * @param annotationClass
     * @param destinationClassName
     * @param destinationClass
     * @param associator
     * @param userEditable HACK: true if this should be on the linking panel
     */
    public <S, A, D> void addAssociator(String sourceClassName,
                                        Class<S> sourceClass,
                                        Class<A> annotationClass,
                                        String destinationClassName,
                                        Class<D> destinationClass,
                                        Associator<S, A, D> associator,
                                        int uiIndex) {
        AssociatorHandle handle = new AssociatorHandle(sourceClassName,
                sourceClass, annotationClass, destinationClassName,
                destinationClass, uiIndex);
        associators.put(handle, associator);
        fireAssociatorAdded(handle, associator);
    }


    public <S, A, D> void removeAssociator(Class<S> sourceClass,
                                           Class<A> annotationClass,
                                           Class<D> destinationClass) {
        removeAssociator(
                         null,
                         sourceClass,
                         annotationClass,
                         null,
                         destinationClass);
    }


    public <S, A, D> void removeAssociator(String sourceClassName,
                                           Class<S> sourceClass,
                                           Class<A> annotationClass,
                                           String destinationClassName,
                                           Class<D> destinationClass) {
        AssociatorHandle handle = new AssociatorHandle(sourceClassName,
                sourceClass, annotationClass, destinationClassName,
                destinationClass);
        Associator<S, A, D> associator = (Associator<S, A, D>) associators
                .remove(handle);
        fireAssociatorRemoved(handle, associator);
    }


    public <K, V> void addMap(String mapName,
                              Class<K> keyClass,
                              Class<V> valueClass,
                              NotifyingMap<K, V> map) {
        ClassPair classPair = new ClassPair(mapName, keyClass, valueClass);
        maps.put(classPair, map);
    }


    public <K, V> NotifyingMap<K, V> getMap(String mapName,
                                            Class<K> keyClass,
                                            Class<V> valueClass) {
        ClassPair classPair = new ClassPair(mapName, keyClass, valueClass);
        return maps.get(classPair);
    }


    public Map<ListHandle, AbstractNotifyingList> getListMap() {
        return Collections.unmodifiableMap(lists);
    }


    public AbstractNotifyingList<?> getList(String listName, String className)
            throws ClassNotFoundException {
        return getList(listName, Class.forName(className));
    }


    public <O> AbstractNotifyingList<O> getList(String listName, Class<O> cl) {
        return getList(new ListHandle(listName, cl));
    }


    public AbstractNotifyingList<?> getList(String className)
            throws ClassNotFoundException {
        return getList(Class.forName(className));
    }


    public <O> AbstractNotifyingList<O> getList(Class<O> cl) {
        return getList(new ListHandle(cl));
    }


    public <O> AbstractNotifyingList<O> getList(ListHandle handle) {
        return (AbstractNotifyingList<O>) lists.get(handle);
    }


    public <O> void addList(Class<O> cl, AbstractNotifyingList<O> collection) {
        addList(new ListHandle(cl), collection);
    }


    public <O> void addList(String name,
                            Class<O> cl,
                            AbstractNotifyingList<O> collection) {
        addList(new ListHandle(name, cl), collection);
    }


    public <O> void addList(ListHandle handle,
                            AbstractNotifyingList<O> collection) {
        lists.put(handle, collection);
        fireListAdded(handle, collection);
    }


    public <O> void removeList(Class<O> cl) {
        removeList(new ListHandle(cl));
    }


    public <O> void removeList(String name, Class<O> cl) {
        removeList(new ListHandle(name, cl));
    }


    public <O> void removeList(ListHandle listHandle) {
        AbstractNotifyingList<O> list = (AbstractNotifyingList<O>) lists
                .remove(listHandle);
        fireListRemoved(listHandle, list);
    }


    public <O> void addAllocatorModel(Class<O> cl,
                                      AllocatorModel<O> allocatorModel) {
        addAllocatorModel(null, cl, allocatorModel);
    }


    // need to ensure is correct spec used here. No guarantee that is
    public <O> void addAllocatorModel(String listName,
                                      Class<O> cl,
                                      AllocatorModel<O> allocatorModel) {
        allocatorModels.put(new ListHandle(listName, cl), allocatorModel);
        fireAllocatorModelAdded(cl, allocatorModel);
    }


    public <O> void removeAllocatorModel(Class<O> cl) {
        AllocatorModel<O> allocatorModel = (AllocatorModel<O>) allocatorModels
                .remove(cl);
        fireAllocatorModelRemoved(cl, allocatorModel);
    }


    public <O> AllocatorModel<O> getAllocatorModel(Class<O> listClass) {
        return getAllocatorModel(null, listClass);
    }


    public <O> AllocatorModel<O> getAllocatorModel(String listName,
                                                   Class<O> listClass) {
        return allocatorModels.get(new ListHandle(listName, listClass));
    }


    public <O> AllocatorModel<O> getAllocatorModel(ListHandle listHandle) {
        return allocatorModels.get(listHandle);
    }


    public Collection<ListHandle> getAllocatorModelClasses() {
        return allocatorModels.keySet();
    }


    // todo: fire listeners since will need to update interface
    public void setConcept(Class cl, String text) {
        conceptMap.put(cl, text);
    }


    public String getConcept(Class cl) {
        return conceptMap.get(cl);
    }


    public Map<Class, String> getConceptMap() {
        return Collections.unmodifiableMap(conceptMap);
    }


    public void setProperty(String name, Property property) {
        propertyMap.put(name, property);
    }


    public Property getProperty(String name) {
        return propertyMap.get(name);
    }


    public Map<String, Property> getPropertyMap() {
        return Collections.unmodifiableMap(propertyMap);
    }


    public void addListener(PedagogyPlanListener listener) {
        listeners.add(listener);
    }


    public void removeListener(PedagogyPlanListener listener) {
        listeners.add(listener);
    }


    private <S, A, D> void fireAssociatorAdded(AssociatorHandle handle,
                                               Associator<S, A, D> associator) {
        AssociatorPedagogyPlanEvent e = new AssociatorPedagogyPlanEvent(this,
                handle, associator);
        for (PedagogyPlanListener listener : listeners)
            listener.associatorAdded(e);
    }


    private <S, A, D> void fireAssociatorRemoved(AssociatorHandle handle,
                                                 Associator<S, A, D> associator) {
        AssociatorPedagogyPlanEvent e = new AssociatorPedagogyPlanEvent(this,
                handle, associator);
        for (PedagogyPlanListener listener : listeners)
            listener.associatorRemoved(e);
    }


    private <O> void fireListAdded(ListHandle listHandle,
                                   AbstractNotifyingList<O> list) {
        // todo: use ListHandle instance for event?
        String name = listHandle.getName();
        Class listClass = listHandle.getListClass();
        ListPedagogyPlanEvent e = new ListPedagogyPlanEvent(this, name,
                listClass, list);
        for (PedagogyPlanListener listener : listeners)
            listener.listAdded(e);
    }


    private <O> void fireListRemoved(ListHandle listHandle,
                                     AbstractNotifyingList<O> list) {
        String name = listHandle.getName();
        Class listClass = listHandle.getListClass();
        ListPedagogyPlanEvent e = new ListPedagogyPlanEvent(this, name,
                listClass, list);
        for (PedagogyPlanListener listener : listeners)
            listener.listRemoved(e);
    }


    private <O> void fireAllocatorModelAdded(Class<O> allocatorClass,
                                             AllocatorModel<O> allocatorModel) {
        AllocatorModelPedagogyPlanEvent e = new AllocatorModelPedagogyPlanEvent(
                this, allocatorClass, allocatorModel);
        for (PedagogyPlanListener listener : listeners)
            listener.allocatorModelAdded(e);
    }


    private <O> void fireAllocatorModelRemoved(Class<O> allocatorClass,
                                               AllocatorModel<O> allocatorModel) {
        AllocatorModelPedagogyPlanEvent e = new AllocatorModelPedagogyPlanEvent(
                this, allocatorClass, allocatorModel);
        for (PedagogyPlanListener listener : listeners)
            listener.allocatorModelRemoved(e);
    }


    /**
     * Notify the change listeners of a change to this instance.
     * 
     * Sub-classes can use this method to notify listeners of changes
     * to other fields.
     * 
     */
    protected void fireStateChanged() {
        changeSupport.fireStateChanged();
    }


    /**
     * Add a change listener to this instance.
     * 
     * This listener is notified of changes to the title or
     * description of this instance and any further field changes that
     * sub-classes may implement.
     * 
     * @param listener
     *            the listener to add
     * 
     */
    public void addChangeListener(ChangeListener listener) {
        changeSupport.addListener(listener);
    }


    /**
     * Remove the specified change listener.
     * 
     * @param listener
     *            the listener to remove
     * 
     */
    public void removeChangeListener(ChangeListener listener) {
        changeSupport.removeListener(listener);
    }


    /**
     * Sets the dirty flag
     */
    public void setDirty(boolean b) {
        dirtyFlag = b;
    }


    public boolean getDirty() {
        return dirtyFlag;
    }

}
